package com.xyy.saas.payment.api.impl;


import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import com.xyy.saas.payment.adpater.pingxx.domain.PayDO;
import com.xyy.saas.payment.callback.core.handler.PaySuccessHandler;
import com.xyy.saas.payment.cashier.core.service.CashierService;
import com.xyy.saas.payment.common.Constants;
import com.xyy.saas.payment.common.exception.PaymentException;
import com.xyy.saas.payment.cores.api.CashierDeskApi;
import com.xyy.saas.payment.cores.bo.CashierPrePayBO;
import com.xyy.saas.payment.cores.bo.PrepayBo;
import com.xyy.saas.payment.cores.bo.PrepayJumpCashierBo;
import com.xyy.saas.payment.cores.enums.ChannelEnum;
import com.xyy.saas.payment.cores.enums.ResultCodeEnum;
import com.xyy.saas.payment.cores.vo.CashierPrePayVO;
import com.xyy.saas.payment.cores.vo.PrepayJumpCashierVo;
import com.xyy.saas.payment.cores.vo.ResultVO;
import com.xyy.saas.payment.payment.core.dto.PrepayDto;
import com.xyy.saas.payment.payment.core.service.PaymentService;
import com.xyy.saas.payment.util.ApolloUtil;
import com.xyy.saas.payment.util.DateUtil;
import com.xyy.saas.payment.util.JSONUtils;
import com.xyy.saas.payment.util.LeafUtil;
import com.xyy.saas.payment.util.SignUtil;
import com.xyy.util.StringUtil;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.Optional;

/**
 * 收银台接口实现
 * @author dake
 * @date 20200327
 *
 */
@Service(version = "1.0.0")
public class CashierDeskApiImpl implements CashierDeskApi {

    private static final Logger LOGGER = LoggerFactory.getLogger(CashierDeskApiImpl.class);

    @Autowired
    private CashierService cashierService;
    @Autowired
    private PaymentService paymentService;
    @Autowired
    private PaySuccessHandler paySuccessHandler;
    @Autowired
    private LeafUtil leafUtil;

    @Override
    public ResultVO<CashierPrePayBO> prepay(CashierPrePayVO cashierPrePayVO, String sign) {
        if(cashierPrePayVO == null || cashierPrePayVO.getBusinessIdType() == null || cashierPrePayVO.getInvoker() == null
                || cashierPrePayVO.getBusinessOrderType() == null || cashierPrePayVO.getBusinessType() == null)
        {
            return ResultVO.create(ResultCodeEnum.PARAM_ERROR);
        }

        try {
            LOGGER.info("CashierDeskApiImpl#prepay, CashierPrePayVO: {}", JSONUtils.toJSON(cashierPrePayVO));
            checkSign(cashierPrePayVO, sign);
            com.xyy.saas.payment.cashier.core.vo.CashierPrePayVO prePayVO = genCashierPrePayVo(cashierPrePayVO);
            processTimeExpire(prePayVO);
            com.xyy.saas.payment.cashier.core.bo.CashierPrePayBO prePayBO = cashierService.prepay(prePayVO);
            ResultVO resultVO = ResultVO.createSuccess(prePayBO.convert2ApiBo());
            LOGGER.info("CashierDeskApiImpl#prepay end CashierPrePayVO: {} result:{}",  JSONUtils.toJSON(cashierPrePayVO) ,JSON.toJSONString(resultVO));
            return resultVO;
        }
        catch(PaymentException e) {
            LOGGER.error("CashierDeskApiImpl CashierDeskApiImpl exception: ",  e);
            ResultCodeEnum codeEnum = e.getCodeEnum();
            return codeEnum == null ? ResultVO.createError(e.getErrMsg()) : ResultVO.createError(codeEnum);
        }
        catch(Exception e) {
            LOGGER.error("CashierDeskApiImpl CashierDeskApiImpl exception: ",  e);
            return ResultVO.createError(e.getMessage());
        }
    }

    /**
     * businessOrderNo, businessOrderType, callback, amount
     * @param cashierPrePayVO
     * @param sign
     */
    private void checkSign(CashierPrePayVO cashierPrePayVO, String sign) throws Exception {
        String publicKey = ApolloUtil.getProperty(cashierPrePayVO.getInvoker() + Constants.PUBLIC);
        if (!SignUtil.doCheck(cashierPrePayVO, sign, publicKey)) {
            throw new PaymentException(ResultCodeEnum.SIGN_ERROR);
        }
    }

    private com.xyy.saas.payment.cashier.core.vo.CashierPrePayVO genCashierPrePayVo(CashierPrePayVO vo) {
        com.xyy.saas.payment.cashier.core.vo.CashierPrePayVO result = new com.xyy.saas.payment.cashier.core.vo.CashierPrePayVO();
        result.setPaymentChannel(vo.getPaymentChannel());
        result.setAmount(vo.getAmount());
        result.setSubject(vo.getSubject());
        result.setBody(Optional.ofNullable(vo.getBody()).filter(s -> !StringUtil.isEmpty(s)).orElse(vo.getSubject()));
        result.setCallback(vo.getCallback());
        result.setBusinessId(vo.getBusinessId());
        result.setBusinessOrderNo(vo.getBusinessOrderNo());
        result.setBusinessType(vo.getBusinessType());
        result.setExtra(vo.getExtra());
        result.setInvoker(vo.getInvoker());
        result.setBusinessIdType(vo.getBusinessIdType());
        result.setBusinessOrderType(vo.getBusinessOrderType());
        result.setTimeExpire(vo.getTimeExpire());
        result.setPayerId(vo.getPayerId());
        result.setPayerName(vo.getPayerName());
        result.setSellerName(vo.getSellerName());
        return result;
    }

    private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
    private void processTimeExpire(com.xyy.saas.payment.cashier.core.vo.CashierPrePayVO prePayVO) {
        Map<String, String> map = Optional.ofNullable(JSONUtils.parse2Map(prePayVO.getExtra(), String.class, String.class)).orElse(Maps.newHashMap());
        long defaultExpire;
        try {
            defaultExpire = Long.parseLong(ApolloUtil.getProperty(Constants.CASHIER_PAY_EXPIRE));
        } catch (Exception e) {
            defaultExpire = 24 * 3600L;
        }
        long timeExpire = prePayVO.getTimeExpire() == null || prePayVO.getTimeExpire() == 0 ? defaultExpire : prePayVO.getTimeExpire();
        String timeExpireStr = LocalDateTime.now().plusSeconds(timeExpire).format(formatter);
        prePayVO.setTimeExpire(timeExpire);
        map.put("timeExpire", timeExpireStr);
        prePayVO.setExtra(JSONUtils.toJSON(map));
    }

    @Override
    public ResultVO<PrepayJumpCashierBo> prepayJumpCashier(PrepayJumpCashierVo prepayJumpCashierVo, String sign) {
        LOGGER.info("CashierDeskApiImpl#prepayJumpCashier begin prepayJumpCashierVo:{}", JSON.toJSON(prepayJumpCashierVo));
        try {
            // 参数校验
            paymentService.checkPrepayJumpCashier(prepayJumpCashierVo);
        }
        catch(PaymentException e) {
            LOGGER.warn("CashierDeskApiImpl#prepayJumpCashier warn prepayJumpCashierVo:{} paymentException:{}",
                    JSON.toJSONString(prepayJumpCashierVo), e.getErrCode(), e);
            return ResultVO.createParamError();
        }

        try {
            String publicKey = ApolloUtil.getProperty(prepayJumpCashierVo.getInvoker() + Constants.PUBLIC);

            if (!SignUtil.doCheck(prepayJumpCashierVo, sign, publicKey)) {
                LOGGER.warn("CashierDeskApiImpl#prepayJumpCashier warn prepayJumpCashierVo:{} paymentException:{}",
                        JSON.toJSONString(prepayJumpCashierVo), ResultCodeEnum.SIGN_ERROR.getCode());
                return ResultVO.createSignError();
            }
            if (null != prepayJumpCashierVo.getChannel() && prepayJumpCashierVo.getChannel().getCode().startsWith(Constants.CHANNEL_JDAGGREGATE_NAME)) {
                JSONObject extra = StringUtils.isBlank(prepayJumpCashierVo.getMapJson()) ? new JSONObject() : JSONObject.parseObject(prepayJumpCashierVo.getMapJson());
                extra.put("payMode", null == prepayJumpCashierVo.getCashierEnum() ? "" : prepayJumpCashierVo.getCashierEnum().getMode());
                extra.put("channel", prepayJumpCashierVo.getChannel().getCode());
                extra.put("terminalTypeEnum", null == prepayJumpCashierVo.getTerminalTypeEnum() ? "" : prepayJumpCashierVo.getTerminalTypeEnum().getChannel());
                prepayJumpCashierVo.setMapJson(extra.toJSONString());
            }
            PrepayDto prepayDto = new PrepayDto();
            prepayDto.setBusinessId(prepayJumpCashierVo.getPayeeId());
            prepayDto.setBusinessOrderNo(prepayJumpCashierVo.getBusinessOrderNo());
            prepayDto.setAmount(prepayJumpCashierVo.getAmount());
            prepayDto.setBusinessType(prepayJumpCashierVo.getBusinessType());
            prepayDto.setBusinessIdType(prepayJumpCashierVo.getBusinessIdType());
            prepayDto.setOrderType(prepayJumpCashierVo.getBusinessType());
            prepayDto.setBusinessOrderType(prepayJumpCashierVo.getBusinessOrderType());
            prepayDto.setCallbackUrl(prepayJumpCashierVo.getCallback());
            prepayDto.setChannel(prepayJumpCashierVo.getChannel() != null ? prepayJumpCashierVo.getChannel().getCode() : null);
            prepayDto.setSubject(prepayJumpCashierVo.getSubject());
            prepayDto.setBody(prepayJumpCashierVo.getBody());
            prepayDto.setExtra(prepayJumpCashierVo.getMapJson());
            prepayDto.setPayerId(prepayJumpCashierVo.getPayerId());
            prepayDto.setPayerName(prepayJumpCashierVo.getPayerName());
            prepayDto.setSellerName(prepayJumpCashierVo.getSellerName());
            ResultVO resultVO = paymentService.prepay(prepayDto);
            PrepayBo prepayBo = (PrepayBo)resultVO.getResult();
            prepayJumpCashierVo.setAccountId(prepayBo.getUserId());
            PrepayJumpCashierBo prepayJumpCashierBo = cashierService.getCashierPath(prepayJumpCashierVo, prepayBo.getPayNo());
            LOGGER.info("CashierDeskApiImpl#prepayJumpCashier finish prepayJumpCashierVo:{}  prepayJumpCashierBo:{}",
                    JSON.toJSONString(prepayJumpCashierVo), JSON.toJSONString(prepayJumpCashierBo));

            // ----mock begin----
            tryMockPay(prepayBo.getPayNo(), prepayDto);
            // ----mock end----

            return ResultVO.createSuccess(prepayJumpCashierBo);
        }
        catch(PaymentException e) {
            LOGGER.error("CashierDeskApiImpl#prepayJumpCashier warn prepayJumpCashierVo:{} paymentException:{}",
                    JSON.toJSONString(prepayJumpCashierVo), e.getErrCode(), e);
            return ResultVO.create(e.getErrCode());
        }
        catch(Exception e) {
            LOGGER.error("CashierDeskApiImpl#prepayJumpCashier error prepayJumpCashierVo:{}", JSON.toJSONString(prepayJumpCashierVo), e);
            return ResultVO.create(ResultCodeEnum.DEFAULT_ERROR);
        }
    }

    /**
     * Mock支付
     */
    private void tryMockPay(String payNo, PrepayDto prepayDto) {
        try {
            if(Boolean.TRUE.equals(Boolean.parseBoolean(ApolloUtil.getProperty("mock_pay_switch", "false")) &&
                    ApolloUtil.getProperty("mock_pay_business_order_type", StringUtils.EMPTY).contains(prepayDto.getBusinessOrderType())))
            {
                LOGGER.info("This is a mock payment for payNo:{}, businessOrderType:{}", payNo, prepayDto.getBusinessOrderType());
                PayDO domain = new PayDO();
                domain.setOrderNo(payNo);
                domain.setChannelChargeNo(leafUtil.getSnowflakeId());
                domain.setAmount(prepayDto.getAmount());
                String mode = !StringUtils.isBlank(prepayDto.getChannel()) && prepayDto.getChannel().contains(ChannelEnum.WX.getCode()) ?
                        ChannelEnum.WX.getCode() : ChannelEnum.ALIPAY.getCode();
                domain.setChannel(Constants.CHANNEL_FUMIN_BANK_NAME + "_" + mode);
                domain.setResultCode("SUCCESS");
                domain.setChannelTransactionNo(leafUtil.getSnowflakeId());
                domain.setTimePaid(DateUtil.now());
                paySuccessHandler.callBackHandler(domain);
            }
        }
        catch(Exception ex) {
            LOGGER.error("Mock支付 error, payNo=" + payNo + ", prepayDto=" + JSONUtils.toJSON(prepayDto) + ", errorMsg=" + ex.getMessage(), ex);
        }
    }
}
